﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Dpy.Math.Algebra
{
    public class Vector
    {
        #region Fields

        private double[] vector;

        #endregion


        #region Properties

        public int Dimension
        {
            get
            {
                return this.vector.Length;
            }
        }

        public double this[int index]
        {
            get
            {
                return this.vector[index];
            }
            set
            {
                this.vector[index] = value;
            }
        }

        public double Length
        {
            get
            {
                return System.Math.Sqrt(this * this);
            }
        }

        #endregion


        #region Constructors

        public Vector(int dimension)
            :this(new double[dimension])
        {
        }

        public Vector(IEnumerable<double> elements)
        {
            this.vector = elements.ToArray();

            if (this.Dimension <= 0)
            {
                throw new ArgumentException("Vector dimension must be greater than zero!", "dimension");
            }
        }

        #endregion


        #region Methods

        public static double GetAngleInRadians(Vector a, Vector b)
        {
            double lengths = a.Length * b.Length;

            if (lengths.IsZero())
            {
                throw new ArgumentException("Cannot find angle between vector and a zero vector!");
            }

            return System.Math.Acos((a * b) / lengths);
        }

        public bool IsOrthogonalWith(Vector other)
        {
            return (this * other).IsZero();
        }

        public void Scale(double coeficient)
        {
            this.vector = (this * coeficient).vector;
        }

        public void Reverse()
        {
            this.Scale(-1);
        }

        public Vector Clone()
        {
            return new Vector(this.vector);
        }

        public bool ColinearsWith(Vector vector)
        {
            if (this.Dimension != vector.Dimension)
            {
                throw new ArgumentException("Vectors must be of same dimension in order to check for colinearity!");
            }

            if (this.IsZero() || vector.IsZero())
            {
                return true;
            }

            int nonZeroIndex = this.Dimension;
            double ratio = 0;

            for (int i = 0; i < this.Dimension; i++)
            {
                if (this[i].IsZero() && vector[i].IsZero())
                {
                    continue;
                }

                if (this[i].IsZero() ^ vector[i].IsZero())
                {
                    return false;
                }
                else
                {
                    nonZeroIndex = i;
                    ratio = this[nonZeroIndex] / vector[nonZeroIndex];
                    break;
                }                
            }

            for (int i = nonZeroIndex + 1; i < this.Dimension; i++)
            {
                if (!(this[i]).EssentiallyEquals(ratio * vector[i]))
                {
                    return false;
                }
            }

            return true;
        }

        public bool TryNormalize()
        {
            double length = this.Length;

            if (!length.IsZero())
            {
                this.Scale(1 / length);
                return true;
            }

            return false;
        }

        public bool IsUnit()
        {
            return this.Length.EssentiallyEquals(1);
        }

        public bool IsZero()
        {
            return this.Length.IsZero();
        }

        public Matrix ToColumnMatrix()
        {
            Matrix result = new Matrix(this.Dimension, 1);
            for (int i = 0; i < this.Dimension; i++)
            {
                result[i, 0] = this[i];
            }

            return result;
        }

        public Matrix ToRowMatrix()
        {
            Matrix result = new Matrix(1, this.Dimension);
            for (int i = 0; i < this.Dimension; i++)
            {
                result[0, i] = this[i];
            }

            return result;
        }

        public bool EssentiallyEquals(Vector other)
        {
            return Vector.AreEssentiallyEqual(this, other);
        }

        public static bool AreEssentiallyEqual(Vector a, Vector b)
        {
            if (a.Dimension != b.Dimension)
            {
                throw new ArgumentException("Vectors must be of same dimensions to be compared!");
            }

            for (int i = 0; i < a.Dimension; i++)
            {
                if (!a[i].EssentiallyEquals(b[i]))
                {
                    return false;
                }
            }

            return true;
        }

        #endregion


        #region Operators

        public static Vector operator *(Matrix a, Vector v)
        {
            Matrix columnVector = v.ToColumnMatrix();

            return (a * columnVector).ColumnVector(0);
        }

        public static Vector operator *(Vector v, Matrix a)
        {
            Matrix rowVector = v.ToRowMatrix();

            return (rowVector * a).RowVector(0);
        }

        public static double operator *(Vector a, Vector b)
        {
            if (a.Dimension != b.Dimension)
            {
                throw new ArgumentException("Multiplied vectors must be of same dimensions");
            }

            double sum = 0;

            for (int i = 0; i < a.Dimension; i++)
            {
                sum += a[i] * b[i];
            }

            return sum;
        }

        public static Vector operator *(double number, Vector vector)
        {
            Vector result = new Vector(vector.Dimension);

            for (int i = 0; i < vector.Dimension; i++)
            {
                result[i] = number * vector[i];
            }

            return result;
        }

        public static Vector operator *(Vector vector, double number)
        {
            return number * vector;
        }

        public static Vector operator +(Vector a, Vector b)
        {
            if (a.Dimension != b.Dimension)
            {
                throw new ArgumentException("Summed vectors must be of same dimensions");
            }

            Vector result = new Vector(a.Dimension);

            for (int i = 0; i < a.Dimension; i++)
            {
                result[i] = a[i] + b[i];
            }

            return result;
        }

        public static Vector operator -(Vector vector)
        {
            return -1 * vector;
        }

        public static Vector operator -(Vector a, Vector b)
        {
            return a + (-b);
        }

        #endregion


        #region Overriden Methods

        public override string ToString()
        {
            StringBuilder result = new StringBuilder();
            string listSeparator = Dpy.Math.Utility.CurrentCulture.TextInfo.ListSeparator;

            result.AppendFormat("({0}", this[0]);

            for (int i = 1; i < this.Dimension; i++)
            {
                result.AppendFormat("{0} {1}", listSeparator, this[i]);
            }

            result.Append(")");

            return result.ToString();
        }

        #endregion
    }
}
